/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2019-2019.
 * Description: fiq_routeway_register.c
 * Author: xiaojiangfeng <xiaojiangfeng@huawei.com>
 * Create: 2019-01-08
 */

#include <linux/types.h>
#include <linux/of_irq.h>
#include <linux/of_address.h>
#include <linux/delay.h>
#include <linux/acpi.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/hal/cpu_param.h>
#include <linux/hal/gpio_lock.h>
#include <linux/rtos_acpi_id.h>
#include "fiq_routeway_register.h"
#include "fiq_glue_private.h"
#ifdef CONFIG_RTOS_EARLY_KBOX
#include <linux/early_kbox.h>
#endif

static uint32_t fiq_glue_params[FIQ_GLUE_NUM_MAX][FIQ_GLUE_INT_PARAM_MAX];

struct fiq_glue_no_info g_fiq_glue_no_info;

#ifdef CONFIG_RTOS_HAL_FIQ_GLUE_EARLY
int g_fiq_early_mode;
#endif

/* A reference is gic_irq_domain_translate */
uint32_t fiq_glue_no_translate(uint32_t *params)
{
	switch (params[0]) {
	case 0:		/* SPI */
		return params[1] + 32;
	case 1:		/* PPI */
		return params[1] + 16;
	default:
		return params[1];
	}
}

int fiq_glue_parse_source_common(const char *string)
{
	if (!strcmp(string, FIQ_WDG_SOURCE_STRING)) {
		fiq_priv.fiq_source = FIQ_WDG_SOURCE;
	} else if (!strcmp(string, FIQ_GIC_SOURCE_STRING)) {
		fiq_priv.fiq_source = FIQ_GIC_SOURCE;
	} else {
		pr_err("%s fail to recognize string %s\n", __func__, string);
		fiq_priv.fiq_source = FIQ_UNKNOWN_SOURCE;
		return -EINVAL;
	}
	pr_info("%s fiq source is %d!\n", __func__, fiq_priv.fiq_source);

	return 0;
}

static int fiq_glue_parse_source(struct device *dev)
{
	int err;
	const char *string = NULL;

	fiq_priv.fiq_source = FIQ_UNKNOWN_SOURCE;
	err = device_property_read_string(dev, FIQ_SOURCE_STRING, &string);
	fiq_glue_log_and_return_if(err, err, "%s %s does not exist", __func__, FIQ_SOURCE_STRING);

	return fiq_glue_parse_source_common(string);
}

int fiq_glue_parse_type_common(const char *string)
{
	if (!strcmp(string, FIQ_1_N_TYPE_STRING)) {
		fiq_priv.fiq_type = FIQ_1_N_TYPE;
	} else if (!strcmp(string, FIQ_N_N_TYPE_STRING)) {
		fiq_priv.fiq_type = FIQ_N_N_TYPE;
	} else {
		pr_err("%s fail to recognize string %s\n", __func__, string);
		fiq_priv.fiq_type = FIQ_UNKNOWN_TYPE;
		return -EINVAL;
	}
	pr_info("%s fiq type is %d!\n", __func__, fiq_priv.fiq_type);

	return 0;
}

static int fiq_glue_parse_type(struct device *dev)
{
	int err;
	const char *string = NULL;

	fiq_priv.fiq_type = FIQ_UNKNOWN_TYPE;
	err = device_property_read_string(dev, FIQ_TYPE_STRING, &string);
	fiq_glue_log_and_return_if(err, err, "%s %s does not exist", __func__, FIQ_TYPE_STRING);

	return fiq_glue_parse_type_common(string);
}

bool cpuid_is_continuous;

static void fiq_glue_parse_buffer_offset_type(struct device *dev)
{
	if (device_property_present(dev, FIQ_CPUID_IS_CONTINUOUS)) {
		cpuid_is_continuous = true;
		pr_info("%s fiq get cpu id type is general!\n", __func__);
	} else {
		cpuid_is_continuous = false;
		pr_info("%s fiq get cpu id type is default!\n", __func__);
	}
}

static void fiq_glue_parse_clean_wdt(struct device *dev)
{
	(void)device_property_read_u32(dev, FIQ_CLEAN_WTG, &fiq_priv.fiq_clean_wtg);
	pr_info("%s fiq clean-wdt is %u!\n", __func__, fiq_priv.fiq_clean_wtg);
}

static void fiq_glue_parse_dead_loop(struct device *dev)
{
	fiq_priv.fiq_deadloop = true;
	(void)device_property_read_u32(dev, FIQ_DEADLOOP, &fiq_priv.fiq_deadloop);
	pr_info("%s fiq dead-loop is %u!\n", __func__, fiq_priv.fiq_deadloop);
}

static void fiq_glue_parse_invalid_cache(struct device *dev)
{
	(void)device_property_read_u32(dev, FIQ_NEED_INVALID_CACHE, &fiq_priv.need_invalid_cache);
	pr_info("%s fiq invalid-cache is %u!\n", __func__, fiq_priv.need_invalid_cache);
}

static int fiq_glue_no_info_init(struct device *dev)
{
	int err;
	uint32_t i;

	err = device_property_read_u32(dev, "fiq_nums", &g_fiq_glue_no_info.fiq_num);
	fiq_glue_log_and_return_if(err, err, "%s fiq_nums does not exist", __func__);

	pr_info("%s fiq_num is %u, fiq_num_max is %d\n", __func__, g_fiq_glue_no_info.fiq_num, FIQ_GLUE_NUM_MAX);

	if (g_fiq_glue_no_info.fiq_num > FIQ_GLUE_NUM_MAX)
		return -E2BIG;

	memset(&fiq_glue_params[0][0], 0xFF, sizeof(fiq_glue_params));
	device_property_read_u32_array(dev, "interrupts", &fiq_glue_params[0][0],
		FIQ_GLUE_INT_PARAM_MAX * g_fiq_glue_no_info.fiq_num);
	fiq_glue_log_and_return_if(err, err, "%s fail to parse", __func__);

	for (i = 0; i < g_fiq_glue_no_info.fiq_num; i++) {
		g_fiq_glue_no_info.fiq_no[i] = fiq_glue_no_translate(fiq_glue_params[i]);
		pr_info("%s i %u fiq-no %u\n", __func__, i, g_fiq_glue_no_info.fiq_no[i]);
	}

	return 0;
}

static void fiq_glue_reg_init(void *args)
{
	uint32_t reg_val;
	struct fiq_glue_reg_cfg *fiq = (struct fiq_glue_reg_cfg *)args;

	rtos_gpio_lock();
	reg_val = readl(fiq->addr);
	reg_val &= ~(fiq->mask);
	reg_val |= fiq->val;
	writel(reg_val, fiq->addr);
	rtos_gpio_unlock();
}

int fiq_default_init(void)
{
	int err;
	int i;
	uint32_t mask, val, bank;
	void __iomem *addr = NULL;
	struct device_node *node = NULL;
	struct device_node *np = NULL;
	struct fiq_glue_reg_cfg fiq;

#ifdef CONFIG_ACPI
	if (!acpi_disabled)
		return 0;
#endif

	node = of_find_compatible_node(NULL, NULL, FIQ_DTS_COMPATIBLE_STRING);
	fiq_glue_log_and_return_if(!node, -ENODEV, "%s hisi_fiq does not exist", __func__);

	i = 0;
	for_each_child_of_node(node, np) {
		i++;
		err = of_property_read_u32(np, "mask", &mask);
		fiq_glue_log_and_return_if(err, -EINVAL, "%s %u fail to get mask", __func__, i);

		err = of_property_read_u32(np, "val", &val);
		fiq_glue_log_and_return_if(err, -EINVAL, "%s %u fail to get val", __func__, i);

		addr = of_iomap(np, 0);
		fiq_glue_log_and_return_if(!addr, -ENOMEM, "%s %u fail to iomap", __func__, i);

		fiq.addr = addr;
		fiq.mask = mask;
		fiq.val	= val;
		bank = 0;
		/* Dts does not have this field when register is not banked */
		(void)of_property_read_u32(np, "bank", &bank);
		if (bank)
			smp_call_function(fiq_glue_reg_init, &fiq, true);
		 fiq_glue_reg_init(&fiq);

		iounmap(addr);

		/* sleep 10ms, wait io stable; don't delete it */
		msleep(10);
	}

	return 0;
}

static int fiq_glue_probe(struct platform_device *pdev)
{
	int err;

	err = fiq_glue_parse_source(&pdev->dev);
	fiq_glue_log_and_return_if(err, err, "%s fail to parse source", __func__);

	err = fiq_glue_parse_type(&pdev->dev);
	fiq_glue_log_and_return_if(err, err, "%s fail to parse type", __func__);

	fiq_glue_parse_buffer_offset_type(&pdev->dev);
	fiq_glue_parse_clean_wdt(&pdev->dev);
	fiq_glue_parse_dead_loop(&pdev->dev);
	fiq_glue_parse_invalid_cache(&pdev->dev);

	err = fiq_glue_no_info_init(&pdev->dev);
	fiq_glue_log_and_return_if(err, err, "%s fail to init fiq-no-info", __func__);

	fiq_register_handle(fiq_default_init, NULL);
	proc_fiqstat_init();
	return 0;
}

static const struct of_device_id fiq_glue_of_match[] = {
	{ .compatible = FIQ_DTS_COMPATIBLE_STRING },
	{},
};
MODULE_DEVICE_TABLE(of, fiq_glue_of_match);

static const struct acpi_device_id fiq_glue_acpi_match[] = {
	{ RTOS_ACPI_HID_FIQ_GLUE, 0 },
	{},
};
MODULE_DEVICE_TABLE(acpi, fiq_glue_acpi_match);

static struct platform_driver fiq_glue_driver = {
	.driver = {
		.name = "fiq_glue",
		.of_match_table = fiq_glue_of_match,
		.acpi_match_table = ACPI_PTR(fiq_glue_acpi_match),
	},
	.probe = fiq_glue_probe,
};

module_platform_driver(fiq_glue_driver);

#ifdef CONFIG_RTOS_EARLY_KBOX
static int fiq_glue_ekbox_buffer_init(void)
{
	if (ekbox_pcontent) {
#ifdef CONFIG_RTOS_HAL_CORE_RESET_LOG
		/*
		 * 1381-olt support 16 cores, fiq will use 16 * 2 bit for log, but
		 * reserve 32 * 2 bit.
		 * use last 32 bit for slave boot log and don't memset in th.
		 */
		memset(ekbox_pcontent, 0x20, EKBOX_RESERVE_SIZE / 2 - 1);
#else
		memset(ekbox_pcontent, 0x20, EKBOX_RESERVE_SIZE - 1);
#endif
		memset(ekbox_pcontent + EKBOX_RESERVE_SIZE - 1, '\n', 1);
	}
	return 0;
}

static int fiq_glue_notifier_event(struct notifier_block *this, unsigned long action, void *unused)
{
	pr_info("fiq-glue ekbox buffer init\n");
	return fiq_glue_ekbox_buffer_init();
}

static struct notifier_block fiq_glue_notifier = {
	.notifier_call = fiq_glue_notifier_event,
};

static int __init fiq_glue_ekbox_init(void)
{
	(void)register_ekbox_reserved_mem_format_notifier(&fiq_glue_notifier);
	return fiq_glue_ekbox_buffer_init();
}

arch_initcall(fiq_glue_ekbox_init);
#endif

